
// C API
#include <string.h>
#include <assert.h>
#include <unistd.h>
// C++ STL
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
// owner
#include "Endian.hpp"

#ifndef BUFFER_HPP
#define BUFFER_HPP

namespace tulun
{
    class Buffer
    {
    private:
        static const size_t kCheapPrepend = 8;
        static const size_t kInitialSize = 1024;
        static const char kCRLF[];

    private:
        std::vector<char> buffer_;
        size_t readerIndex_; // 可读数据首地址
        size_t writerIndex_; // 可写数据首地址

        void makeSpace(size_t len)
        {
            if (writableBytes() + prependableBytes() < len + kCheapPrepend)
            {
                buffer_.resize(writerIndex_ + len);
            }
            else
            {
                size_t readable = readableBytes();
                std::copy(begin() + readerIndex_,
                          begin() + writerIndex_,
                          begin() + kCheapPrepend);
                readerIndex_ = kCheapPrepend;
                writerIndex_ = readerIndex_ + readable;
            }
        }
        char *begin()
        {
            return &*buffer_.begin();
        }
        const char *begin() const
        {
            return &*buffer_.begin();
        }

    public:
        Buffer(size_t initialSize = kInitialSize)
            : buffer_(kCheapPrepend + initialSize),
              readerIndex_(kCheapPrepend),
              writerIndex_(kCheapPrepend)
        {
        }
        void swap(Buffer &rhs)
        {
            buffer_.swap(rhs.buffer_);
            std::swap(this->readerIndex_, rhs.readerIndex_);
            std::swap(this->writerIndex_, rhs.writerIndex_);
        }
        size_t readableBytes() const
        {
            return writerIndex_ - readerIndex_;
        }
        size_t writableBytes() const
        {
            return buffer_.size() - writerIndex_;
        }
        size_t prependableBytes() const
        {
            return readerIndex_;
        }
        const char *peek() const
        {
            return begin() + readerIndex_;
        }
        const char *findCRLF() const
        {
            const char *crlf = std::search(peek(), beginWrite(), kCRLF, kCRLF + 2);
            return crlf == beginWrite() ? nullptr : crlf;
        }
        const char *findCRLF(const char *start) const
        {
            assert(peek() <= start);
            assert(start <= beginWrite());
            const char *crlf = std::search(start, beginWrite(), kCRLF, kCRLF + 2);
            return crlf == beginWrite() ? nullptr : crlf;
        }

        const char *findEOL() const
        {
            const void *eol = memchr(peek(), '\n', readableBytes());
            return static_cast<const char *>(eol);
        }
        const char *findEOL(const char *start) const
        {
            assert(peek() <= start);
            assert(start <= beginWrite());
            const void *eol = memchr(start, '\n', readableBytes());
            return static_cast<const char *>(eol);
        }

        void retrieve(size_t len)
        {
            assert(len <= readableBytes());
            if (len < readableBytes())
            {
                readerIndex_ += len;
            }
            else
            {
                retrieveAll();
            }
        }

        std::string retrieveAsString(size_t len)
        {
            assert(len <= readableBytes());
            std::string result(peek(), len);
            retrieve(len);
            return result;
        }
        std::string retrieveAllAsString()
        {
            return retrieveAsString(readableBytes());
        }
        void retrieveAll()
        {
            readerIndex_ = kCheapPrepend;
            writerIndex_ = kCheapPrepend;
        }
        //----------------------
        void ensureWritableBytes(size_t len)
        {
            if (writableBytes() < len)
            {
                makeSpace(len);
            }
        }
        void append(const char *data, int len)
        {
            ensureWritableBytes(len);
            std::copy(data, data + len, beginWrite());
            hasWritten(len);
        }
        void append(const void *data, int len)
        {
            append(static_cast<const char *>(data), len);
        }

        void unwrite(size_t len)
        {
            assert(len <= readableBytes());
            writerIndex_ -= len;
        }

        const char *beginWrite() const
        {
            return begin() + writerIndex_;
        }
        const char *beginWrite()
        {
            return begin() + writerIndex_;
        }
        void hasWritten(size_t len)
        {
            assert(len <= writableBytes());
            writerIndex_ += len;
        }

        void appendInt64(int64_t x)
        {
            int64_t be64 = Sockets::hostToNetwork64(x);
            append(&be64, sizeof be64);
        }
        void appendInt32(int32_t x)
        {
            int32_t be32 = Sockets::hostToNetwork32(x);
            append(&be32, sizeof be32);
        }
        void appendInt16(int16_t x)
        {
            int16_t be16 = Sockets::hostToNetwork16(x);
            append(&be16, sizeof be16);
        }
        void appendInt8(int8_t x)
        {
            append(&x, sizeof x);
        }

        void retrieveInt64()
        {
            retrieve(8);
        }
        void retrieveInt32()
        {
            retrieve(4);
        }
        void retrieveInt16()
        {
            retrieve(2);
        }
        void retrieveInt8()
        {
            retrieve(1);
        }

        int64_t readInt64()
        {
            int64_t result = peekInt64();
            retrieveInt64();
            return result;
        }
        int32_t readInt32()
        {
            int32_t result = peekInt32();
            retrieveInt32();
            return result;
        }
        int16_t readInt16()
        {
            int16_t result = peekInt16();
            retrieveInt16();
            return result;
        }
        int8_t readInt8()
        {
            int8_t result = peekInt8();
            retrieveInt8();
            return result;
        }

        int64_t peekInt64() const
        {
            assert(readableBytes() >= sizeof(int64_t));
            int64_t be64 = 0;
            ::memcpy(&be64, peek(), sizeof be64);
            return Sockets::networkToHost64(be64);
        }
        int32_t peekInt32() const
        {
            assert(readableBytes() >= sizeof(int32_t));
            int32_t be32 = 0;
            ::memcpy(&be32, peek(), sizeof be32);
            return Sockets::networkToHost32(be32);
        }
        int16_t peekInt16() const
        {
            assert(readableBytes() >= sizeof(int16_t));
            int16_t be16 = 0;
            ::memcpy(&be16, peek(), sizeof be16);
            return Sockets::networkToHost16(be16);
        }
        int8_t peekInt8() const
        {
            assert(readableBytes() >= sizeof(int8_t));
            int8_t x = *peek();
            return x;
        }

        void prependInt64(int64_t x)
        {
            int64_t be64 = Sockets::hostToNetwork64(x);
            prepend(&be64, sizeof be64);
        }
        void prependInt32(int32_t x)
        {
            int32_t be32 = Sockets::hostToNetwork32(x);
            prepend(&be32, sizeof be32);
        }
        void prependInt16(int16_t x)
        {
            int16_t be16 = Sockets::hostToNetwork16(x);
            prepend(&be16, sizeof be16);
        }
        void prependInt8(int8_t x)
        {
            prepend(&x, sizeof x);
        }

        void prepend(const void *data, size_t len)
        {
            assert(len <= prependableBytes());
            readerIndex_ -= len;
            const char *d = static_cast<const char *>(data);
            std::copy(d, d + len, begin() + readerIndex_);
        }
        void shrink(size_t reserve)
        {
            Buffer other;
            other.ensureWritableBytes(readableBytes() + reserve);
            this->swap(other);
        }
        size_t internalCapacity() const
        {
            return buffer_.capacity();
        }

        ssize_t readFd(int fd, int *savedError);

        ssize_t writeFd(int fd, int *savedError)
        {
            ssize_t n = ::write(fd, peek(), readableBytes());
            if (n < 0)
            {
                *savedError = errno;
            }
            return n;
        }
    };
} // namespace tulun

#endif